/**
 * Universita' degli Studi di Palermo, 2010-11
 */
package org.unipa.bn.model.bnplanner;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import org.springframework.beans.factory.InitializingBean;
import org.unipa.botplatform.model.Bot;
import org.unipa.botplatform.model.Interaction;
import org.unipa.botplatform.model.Planner;
import org.unipa.botplatform.model.simple.interaction.Dialogue;
import org.unipa.botplatform.util.FileManager;


import smile.Network;

/**
 * Bayesian Network Planner
 *
 * @author Giuseppe Miceli
 */
public class BNPlanner implements Planner, InitializingBean {

	/**
	 * The bot whose behavior will be changed.
	 */
	private Bot bot;

	/**
	 * The Bayesian Network used to plan.
	 */
	private Network net;

	/**
	 * Map with nodes and outcomes whose modification implies bot modification
	 */
	private List<Node> outcomeToPluginMap;

	/**
	 * Path to the file that contains the bayesian network defined through Genie-Smile.
	 */
	private String netFilename;

	/**
	 * Path to the configuration file of the planner.
	 */
	private String configFilename;

	/**
	 * Starting datetime for dialogue evidence extraction
	 */
	private Calendar dialogueSince;

	/**
	 * Engine used to extract evidence from a dialogue.
	 */
	private EvidenceExtractor evidenceExtractor;

	/**
	 * Starts the bayesian network engine loading the network and 
	 * the configuration file.
	 */
	public void afterPropertiesSet() throws Exception {		
		/*	System.out.println(Network.NodeType.Cpt); 		// 18  CPT 
			System.out.println(Network.NodeType.DeMorgan);	// 82
			System.out.println(Network.NodeType.Equation);	// 4
			System.out.println(Network.NodeType.List); 		// 17  Decision
			System.out.println(Network.NodeType.Mau);		// 520
			System.out.println(Network.NodeType.NoisyAdder);// 274
			System.out.println(Network.NodeType.NoisyMax);	// 146
			System.out.println(Network.NodeType.Table); 	// 8   Utility
			System.out.println(Network.NodeType.TruthTable);// 20
		 */
		// Bayesian Network file loading
		net = new Network();

		File netFile = new File(BNPlanner.class.getResource(this.netFilename).getPath());
		if (!netFile.exists()) throw new FileNotFoundException(
				"The Smile Bayesian Network definition file does not exist, or the path is wrong. Path is: " + 
				netFile.getAbsolutePath());
		net.readFile(netFile.getAbsolutePath());

		// Configuration file loading
		URL configFileURL = FileManager.getValidURL(this.configFilename);
		this.outcomeToPluginMap = BNPlannerParser.outcomeToPluginParsing(configFileURL);
	}

	/**
	 * Attach a bot to the Planner. This planner only manage a bot, so if you 
	 * attach a new bot, the old one will be overwritten and the planner will
	 * be started over.
	 * 
	 * @param bot bot to be managed by the planner.
	 */
	public void attachBot(Bot bot) {
		this.bot = bot;
		net.clearAllEvidence();
		net.updateBeliefs();
		this.dialogueSince = new GregorianCalendar();
	}

	/**
	 * @return the configFilename
	 */
	public String getConfigFilename() {
		return configFilename;
	}

	/**
	 * @return the evidenceExtractor
	 */
	public EvidenceExtractor getEvidenceExtractor() {
		return evidenceExtractor;
	}

	/**
	 * @return the netFilename
	 */
	public String getNetFilename() {
		return netFilename;
	}

	/**
	 * Updates bot plugins status. The evolution of these status is driven by an "evidence extractor" 
	 * operating on the dialogue. The extractor tells the planner what outcomes of the bayesian network 
	 * have began evidence: consequently the network is updated and the bot plugins status to follow.
	 */
	public void planThrough(String sessionId, Interaction interaction) {
		Dialogue dialogue = (Dialogue) interaction;
		// Evidence clearing
		net.clearAllEvidence();

		// New evidence extraction through dialogue
		List<Evidence> evicences = evidenceExtractor.getEvidence((Dialogue)dialogue.interactionSince(dialogueSince));

		// Evidence setting
		for (Evidence evidence:evicences){
			net.setEvidence(evidence.getNodeId(), evidence.getOutcomeId());
		}

		// Beliefs updating
		net.updateBeliefs();

		// Bot plugins status updating
		this.updateBotPlugins(sessionId);
	}

	/**
	 * @param configFilename the configFilename to set
	 */
	public void setConfigFilename(String configFilename) {
		this.configFilename = configFilename;
	}

	/**
	 * @param evidenceExtractor the evidenceExtractor to set
	 */
	public void setEvidenceExtractor(EvidenceExtractor evidenceExtractor) {
		this.evidenceExtractor = evidenceExtractor;
	}

	/**
	 * @param netFilename the netFilename to set
	 */
	public void setNetFilename(String netFilename) {
		this.netFilename = netFilename;
	}

	/**
	 * Brings back the Bayesian Network to the initial status and 
	 * it updates the bot plugins consequently.
	 */
	public void startOver(String sessionId) {
		net.clearAllEvidence();
		net.updateBeliefs();
		this.dialogueSince = new GregorianCalendar();
		this.updateBotPlugins(sessionId);
	}

	/**
	 * @param sessionId 
	 * 
	 */
	private void updateBotPlugins(String sessionId){
		// Decision node values extraction
		for (Node node : this.outcomeToPluginMap){
			// Values of every outcome of the node
			double[] values = net.getNodeValue(node.getNodeId());

			for (int i = 0; i < values.length; i++){
				System.out.println("\t[" + 
						net.getOutcomeId(node.getNodeId(), i) + ": " + values[i] + "]");
			}

			// bot Plugins set ON or OFF according the limit values
			for (Outcome outcome:node.getOutcomes()){
				if (values[outcome.getIndex()] < outcome.getLimitValue()){
					bot.setProperty(sessionId, outcome.getPluginName(), "OFF");
				} else {
					bot.setProperty(sessionId, outcome.getPluginName(), "ON");				
				}
			}
		}
	}
}